package com.lry.pda.bottom.c.backend.type;

import org.apache.commons.collections4.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TypeSystem {

    private static TypeSystem self = null;
    public static TypeSystem getSelf() {
        if (self == null) {
            self = new TypeSystem();
        }
        return self;
    }
    private TypeSystem() {

    }

    //<symbolName,symbols>
    private Map<String, List<Symbol>> symbolMap = new HashMap<>();
    //<tag,struct>
    private Map<String, StructDefine> structMap = new HashMap<>();

    public void clear(){
        symbolMap.clear();
        structMap.clear();
    }
    public List<Symbol> getSymbolsByScope(String scope){
        List<Symbol> symbols = new ArrayList<>();
        for (List<Symbol> symbolList : symbolMap.values()) {
            for (Symbol symbol : symbolList) {
                if(symbol.getScope().equals(scope)){
                    symbols.add(symbol);
                }
            }
        }
        return symbols;
    }

    public void addStruct(StructDefine struct) {
        if (structMap.containsKey(struct.getTag())) {
            System.err.println("Struct with name: " + struct.getTag() + " is already defined");
            return;
        }
        structMap.put(struct.getTag(), struct);
    }

    public StructDefine getStruct(String tag){
        return structMap.get(tag);
    }

    public void addSymbol(Symbol symbol,String scope){
        while(null!=symbol){
            symbol.setScope(scope);
            List<Symbol> symbols = symbolMap.get(symbol.getName());
            if(null==symbols){
                symbols = new ArrayList<>();
                symbols.add(symbol);
                symbolMap.put(symbol.getName(),symbols);
            }else{
                handleDuplicateSymbol(symbol, symbols);
            }
            symbol = symbol.getNext();
        }
    }

    private void handleDuplicateSymbol(Symbol symbol, List<Symbol>symList) {
        for (Symbol sym : symList) {
            if (sym.equals(symbol)) {
                System.err.println("Symbol definition replicate: " + sym.getName());
                System.exit(1);
            }
        }
        symList.add(symbol);
    }


    public List<Symbol> getSymbols(String symbolName){
        return symbolMap.get(symbolName);
    }


    /**
     *
     * @param typeText int long double char...
     * @return TypeLink(Specifier sp)
     */
    public TypeLink newType(String typeText){
        int type = Specifier.NONE;
        boolean isSigned = true;
        switch (typeText){
            case "void":
                type = Specifier.VOID;
                break;
            case "char":
                type = Specifier.CHAR;
                break;
            case "short":
                type = Specifier.SHORT;
                break;
            case "int":
                type = Specifier.INT;
                break;
            case "boolean":
                type = Specifier.BOOLEAN;
                break;
            case "float":
                type = Specifier.FLOAT;
                break;
            case "double":
                type = Specifier.DOUBLE;
                break;
            case "long":
                type = Specifier.LONG;
                break;
            case "unsigned":
                isSigned = false;
                break;
        }

        Specifier sp = new Specifier(type,isSigned);
        return new TypeLink(sp);
    }

    public TypeLink newClass(String classText) {
        Specifier sp = new Specifier();
        sp.setType(Specifier.NONE);
        setClassType(sp, classText.charAt(0));

        return new TypeLink(sp);
    }

    private void setClassType(Specifier sp, char c) {
        switch(c) {
            case 0:
                //0? todo
                sp.setStorageClass(Specifier.FIXED);
                sp.setStatic(false);
                sp.setExternal(false);
                break;
            case 't':
                sp.setStorageClass(Specifier.TYPEDEF);
                break;
            case 'r':
                sp.setStorageClass(Specifier.REGISTER);
                break;
            case 's':
                sp.setStatic(true);
                break;
            case 'e':
                sp.setExternal(true);
                break;

            default:
                System.err.println("Internal error, Invalid Class type");
                System.exit(1);
                break;
        }
    }

    public Declarator addDeclarator(Symbol symbol, int declaratorType) {
        Declarator declarator = new Declarator(declaratorType);
        TypeLink link = new TypeLink(declarator);
        symbol.addTypeLink(link);
        return declarator;
    }

    public void addSpecifier(Symbol symbol,TypeLink specifier) {
        while (symbol != null) {
            symbol.addTypeLink(specifier);
            symbol = symbol.getNext();
        }
    }

    public Symbol getSymbolByText(String text, int level, String scope) {
        if (scope.equals(text)) {
            scope = Symbol.SCOPE;
        }

        List<Symbol> symbolList = self.getSymbols(text);
        if(CollectionUtils.isEmpty(symbolList)){
            return null;
        }

        Symbol symbol = null;
        for (Symbol sym : symbolList) {
            if (symbol == null && sym.getScope().equals(Symbol.SCOPE)) {
                symbol = sym;
            }

            if (sym.getScope().equals(scope)) {
                symbol = sym;
            }

            if (symbol != null && sym.getLevel() >= level) {
                symbol = sym;
            }
        }
        return symbol;
    }



}
